#include <opengl/texture.h>
#include <opengl/application.h>
#include <util/stb_image.h>
#include <glad/glad.h>
#include <iostream>

namespace opengl
{

std::string Texture::textureFolderPath("./resources/textures/");
static Application* application = Application::instance();

//! @brief 构造函数
Texture::Texture(unsigned int index, unsigned int id, unsigned int type):
    _texIndex(index), _texObject(id), _texType(type) {}

//! @brief 加载纹理
unsigned int Texture::create2DTexture(const std::string &textureName, bool reverse,
                                unsigned int sAroundWay,
                                unsigned int tAroundWay,
                                unsigned int minFilterWay, 
                                unsigned int maxFilterWay)
{
    std::string texturePath = Texture::completeTexturePath(textureName);

    stbi_set_flip_vertically_on_load(reverse);
    
    unsigned int textureID;
    glGenTextures(1, &textureID);

    int width, height, nrComponents;
    unsigned char *data = stbi_load(texturePath.c_str(), &width, &height, &nrComponents, 0);
    if (data)
    {
        GLenum format;
        if (nrComponents == 1)
            format = GL_RED;
        else if (nrComponents == 3)
            format = GL_RGB;
        else if (nrComponents == 4)
            format = GL_RGBA;

        glBindTexture(GL_TEXTURE_2D, textureID);
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sAroundWay);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tAroundWay);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilterWay);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, maxFilterWay);

        stbi_image_free(data);
    }
    else
    {
        std::cout << "Texture failed to load at file: " << textureName << std::endl;
        stbi_image_free(data);
    }

    return textureID;
}

//! @brief 获得 2D 纹理对象
Texture* Texture::load2DTexture(const std::string &textureName, bool reverse,
                                unsigned int sAroundWay,
                                unsigned int tAroundWay,
                                unsigned int minFilterWay, 
                                unsigned int maxFilterWay)
{
    std::map<std::string, Texture*>& textures = application->textures();

    auto iter = textures.find(textureName);
    if (iter != textures.end())
        return iter->second;

    unsigned int texObject = Texture::create2DTexture(textureName, reverse, sAroundWay, tAroundWay, minFilterWay, maxFilterWay);
    unsigned int texIndex = textures.size();
    Texture* texture = new Texture(texIndex, texObject, GL_TEXTURE_2D);
    
    textures[textureName] = texture;
    return texture;
}

//! @brief 加载天空盒
unsigned int Texture::createCubemapTexture(const std::vector<std::string>& faces, bool reverse)
{
    stbi_set_flip_vertically_on_load(reverse);

    unsigned int textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

    int width, height, nrChannels;
    for (unsigned int i = 0; i < faces.size(); i++)
    {
        std::string texturePath = Texture::completeTexturePath(faces[i]);
        unsigned char *data = stbi_load(texturePath.c_str(), &width, &height, &nrChannels, 0);
        if (data)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
                         0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
            );
            stbi_image_free(data);
        }
        else
        {
            std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
            stbi_image_free(data);
        }
    }

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    return textureID;
}

//! @brief 获取 立方体纹理 对象
Texture* Texture::loadCubemapTexture(const std::vector<std::string>& faces, bool reverse)
{
    if (faces.size() != 6)
        return nullptr;

    std::map<std::string, Texture*>& textures = application->textures();

    unsigned int texIndex = textures.size();
    unsigned int texObject = opengl::Texture::createCubemapTexture(faces, false);
    Texture* texture = new Texture(texIndex, texObject, GL_TEXTURE_CUBE_MAP);

    textures[ faces[0] + faces[1] ] = texture;
    return texture;
}

//! @brief 获取纹理
Texture* Texture::getTexture(const std::string &filename)
{
    return application->textures()[filename];
}

}